/*
	setup_data_area_64.c

	Contains functionality for setting up the scheduler data area
	prior to use.

	Author: Aidan Goddard 1/7/13
*/

// includes
#include"../kernel/headers/types.h"
#include"../kernel/headers/system_config_tables.h"
#include"../kernel/headers/scheduler_tables.h"
#include"../kernel/headers/scheduler_functions.h"
#include"headers/ISR.h"
#include"headers/printf.h"

// system process thread entry points
extern void proc_system_idle(void);
extern void proc_system_setup(void);

/*
	function to set up a system process and register it with a processor if required.

	param 0 - description pointer
	param 1 - description length
	param 2 - PCB index
	param 3 - entry point address
	param 4 - stack pointer
	param 5 - register with CPU?

	returns nothing
*/
#define NO_REGISTER_CPU 0
#define REGISTER_CPU 1

static int tlc_index = 0;
static void SetupSystemProcess(char *image_name, int image_name_length, char *description, int description_length, int PCB_index, void(*entry_point)(void), uint64_t stack_ptr, int CPU_register)
{
	// setup the PCB
	// get global PID
	uint64_t PID = CreatePID(PCB_index);
	SCHEDULER->PCB[PCB_index].PID = PID;

	// set the state
	SCHEDULER->PCB[PCB_index].state = PROCESS_STATE_ACTIVE;
	SCHEDULER->PCB[PCB_index].active_threads = 1;

	// copy the description string
	if(description_length > 255)
	{
		description_length = 255;
	}

	int i;
	for(i = 0; i < description_length; i++)
	{
		SCHEDULER->PCB[PCB_index].process_description[i] = description[i];
	}
	SCHEDULER->PCB[PCB_index].process_description[description_length] = 0;

	// copy the image name
	if(image_name_length > 255)
	{
		image_name_length = 255;
	}

	for(i = 0; i < image_name_length; i++)
	{
		SCHEDULER->PCB[PCB_index].PDB->image_name[i] = image_name[i];
	}
	SCHEDULER->PCB[PCB_index].process_description[description_length] = 0;


	// now set up the PDB
	SCHEDULER->PCB[PCB_index].PDB->working_directory[0] = 0;
	SCHEDULER->PCB[PCB_index].PDB->thread_usage_map[0] = THREAD_STATE_ACTIVE;
	SCHEDULER->PCB[PCB_index].PDB->thread_usage_map[1] = THREAD_STATE_INACTIVE;

	// set up TCB
	SCHEDULER->PCB[PCB_index].TCB[0].RIP = (uint64_t)entry_point;
	SCHEDULER->PCB[PCB_index].TCB[0].CS = 0x08;
	uint64_t flags;
	__asm__ __volatile__("pushfq; pop %%rax" : "=a"(flags));
	flags = flags | (1 << 9); // enable interrupts in the flag
	SCHEDULER->PCB[PCB_index].TCB[0].RFLAGS = flags;
	SCHEDULER->PCB[PCB_index].TCB[0].RSP = stack_ptr;
	SCHEDULER->PCB[PCB_index].TCB[0].TID_to_wake = 0;
	SCHEDULER->PCB[PCB_index].TCB[0].TID = CreateTID(PID, 0);

	// perform an FXSAVE to get a suitable save state for the FPU registers to begin with
	uint64_t fpu_address = (uint64_t)SCHEDULER->PCB[PCB_index].TCB[0].FPU;
	//printf("\n0x%a", fpu_address);
	__asm__ __volatile__("fxsave (%%rax)"::"a" (fpu_address));

	// now register the process with CPU0's active queue if desired
	if(CPU_register == REGISTER_CPU)
	{
		// grab a TLC
		SCH_TLC *tlc = &(SCHEDULER->TLC[tlc_index++]);

		// fill it with the required information
		tlc->next_TLC = NULL;
		tlc->sleep_offset = 0;
		tlc->PCB_index = PCB_index;
		tlc->TCB_index = 0;


		// check if the CPU0 queue is empty
		if(SCHEDULER->CPU_active_start[0] == NULL)
		{
			// queue was empty, assign this thread to the start and end pointers
			SCHEDULER->CPU_active_start[0] = tlc;
			SCHEDULER->CPU_active_end[0] = tlc;
		}
		else
		{
			// queue is not empty, put it to the end
			SCHEDULER->CPU_active_end[0]->next_TLC = tlc;
			SCHEDULER->CPU_active_end[0] = tlc;
		}
	}
	//printf("\nCPU 0 active start: 0x%a active end: 0x%a", SCHEDULER->CPU_active_start[0], SCHEDULER->CPU_active_end[0]);
	//printf("\nPID: 0x%x TID: 0x%x", SCHEDULER->PCB[PCB_index].PID, SCHEDULER->PCB[PCB_index].TCB[0].TID);
	//printf("\nFLAGS: 0x%x", SCHEDULER->PCB[PCB_index].TCB[0].RFLAGS);
	return;
}

/*
	main setup function

	no params

	returns nothing
*/
void SetupDataArea(void)
{
	// set up fields for first 16 (reserved) processes
	int i, j;
	for(i = 0; i < 16; i++)
	{
		// clear locks
		for(j = 0; j < 32; j++)
		{
			SCHEDULER->PCB[i].lock.entering[j] = 0;
			SCHEDULER->PCB[i].lock.ticket[j] = 0;
		}

		// zero first byte of description (NULL char at front)
		SCHEDULER->PCB[i].process_description[0] = 0;

		// zero PID
		SCHEDULER->PCB[i].PID = 0;

		// zero state
		SCHEDULER->PCB[i].state = 0;

		// set owner ID to system
		SCHEDULER->PCB[i].owner_ID = 0;

		// set PML4 address to 1MB
		SCHEDULER->PCB[i].PML4 = 0x100000;

		// set PDB address
		SCHEDULER->PCB[i].PDB = (SCH_PDB*)(KOFFSET + 0x23b000 + (4096 * i) + 0xb00000);
		//printf("\n0x%a", SCHEDULER->PCB[i].PDB);

		// set TCB address
		SCHEDULER->PCB[i].TCB = (SCH_TCB*)(KOFFSET + 0x24b000 + (4096 * 2 * i) + 0xb00000);
		//printf("\n0x%a", SCHEDULER->PCB[i].TCB);

		// clear resource table address
		SCHEDULER->PCB[i].resource_table = NULL;

		// zero active thread count
		SCHEDULER->PCB[i].active_threads = 0;

		// set max TCB entries (2 for reserved processes)
		SCHEDULER->PCB[i].max_threads_in_TCB = 2;
	}

	// set up the PCB stack and zero global PIDs
	for(i = 16; i < 2048; i++)
	{
		SCHEDULER->PCB_stack[i] = i;
		SCHEDULER->PCB[i].PID = 0;
	}
	SCHEDULER->PCB_stack_index = 16;

	// set up TLC stack (first 32 are reserved for use by the system)
	for(i = 32; i < 8192; i++)
	{
		SCHEDULER->TLC_stack[i] = (SCH_TLC*)(KOFFSET + 0x200000 + (16 * i) + 0xb00000);
		//printf("\nTLC stack entry %u points to 0x%a", i, SCHEDULER->TLC_stack[i]);
	}
	SCHEDULER->TLC_stack_index = 32;

	// clear all 64 lock structures
	for(i = 0; i < 64; i++)
	{
		for(j = 0; j < 32; j++)
		{
			SCHEDULER->CPU_active_lock[i].entering[j] = 0;
			SCHEDULER->CPU_active_lock[i].ticket[j] = 0;
		}
	}

	// zero linked list pointers
	SCHEDULER->sleeping_start = NULL;
	SCHEDULER->sleeping_end = NULL;
	SCHEDULER->waiting_start = NULL;
	SCHEDULER->waiting_end = NULL;

	// set scheduling state to disabled
	SCHEDULER->scheduling_state = SCHEDULING_STATE_DISABLED;

	// zero total process and thread count
	SCHEDULER->total_process_count = 0;
	SCHEDULER->total_thread_count = 0;

	// set the default CPU count
	SCHEDULER->active_CPU_count = 1;
	SCHEDULER->CPU_selection_start = 0;

	// zero all CPU specific values
	for(i = 0; i < 32; i++)
	{
		SCHEDULER->CPU_active_start[i] = NULL;
		SCHEDULER->CPU_active_end[i] = NULL;
		SCHEDULER->CPU_active_thread_count[i] = 0;
		SCHEDULER->CPU_current_state[i] = CPU_NOT_SCHEDULING;
		SCHEDULER->CPU_current_thread[i] = NULL;
		SCHEDULER->CPU_pc_usage_counter[i] = 100;
		SCHEDULER->CPU_pc_usage_final[i] = 0;
		SCHEDULER->CPU_pc_usage_timer[i] = 100;
	}

	// now set up the system idle process (process 0, thread 0)
	char system_idle_description[22] = "The CPU idle process.";
	char system_idle_image[9] = "IDLE.sys";
	SetupSystemProcess(system_idle_image, 9, system_idle_description, 22, 0, proc_system_idle, (KOFFSET + 0x3e00000), NO_REGISTER_CPU);

	// set up the system setup process
	char system_setup_description[26] = "The system setup process.";
	char system_setup_image[18] = "railgun_setup.sys";
	SetupSystemProcess(system_setup_image, 18, system_setup_description, 26, 1, proc_system_setup, (KOFFSET + 0x3d00000), REGISTER_CPU);

	// set total process and thread counts (1:1)
	SCHEDULER->CPU_active_thread_count[0] = 1;
	SCHEDULER->total_process_count = 1;
	SCHEDULER->total_thread_count = 1;
/*
	// set up the CPU monitor process
	char system_CPU_monitor_description[25] = "The CPU monitor process.";
	SetupSystemProcess(system_CPU_monitor_description, 25, 2, proc_system_CPU_monitor, (KOFFSET + 0x3d00000), REGISTER_CPU);

	// set up the system cleanup process
	char system_cleanup_description[36] = "The system process cleanup process.";
	SetupSystemProcess(system_cleanup_description, 36, 3, proc_system_cleanup, (KOFFSET + 0x3c00000), REGISTER_CPU);


	// set up the keyboard process
	char keyboard_process_description[34] = "The PS/2 keyboard monitor process";
	SetupSystemProcess(keyboard_process_description, 34, 4, PS2_KB_main, (KOFFSET + 0x3a00000), REGISTER_CPU);

	// set up the ACDM process
	char ACDM_process_description[18] = "The ACDM process";
	SetupSystemProcess(ACDM_process_description, 18, 5, ACDM_main, (KOFFSET + 0x3900000), REGISTER_CPU);

	// set up the ACDM start screen process
	char ACDM_start_process_description[30] = "The ACDM start screen process";
	SetupSystemProcess(ACDM_start_process_description, 30, 6, ProcStartWindow, (KOFFSET + 0x3800000), REGISTER_CPU);

	// set up the ACDM resource viewer process
	char ACDM_resource_process_description[34] = "The ACDM resource viewer process";
	SetupSystemProcess(ACDM_resource_process_description, 34, 7, ProcResourceWindow, (KOFFSET + 0x3700000), REGISTER_CPU);

	// set up the ACDM process viewer process
	char ACDM_process_process_description[32] = "The ACDM process viewer process";
	SetupSystemProcess(ACDM_process_process_description, 32, 8, ProcProcsWindow, (KOFFSET + 0x3600000), REGISTER_CPU);

	// set up the ACDM window viewer process
	char ACDM_window_process_description[31] = "The ACDM window viewer process";
	SetupSystemProcess(ACDM_window_process_description, 31, 9, ProcWindowsWindow, (KOFFSET + 0x3500000), REGISTER_CPU);

	// set up the system error log process
	char ACDM_error_log_description[34] = "The ACDM system error log process";
	SetupSystemProcess(ACDM_error_log_description, 34, 10, ProcSystemError, (KOFFSET + 0x3400000), REGISTER_CPU);


	// temp - add some dummy threads to the sleep queue to test functionality

	SCHEDULER->sleeping_start = &(SCHEDULER->TLC[16]);
	SCHEDULER->TLC[16].sleep_offset = 10;
	SCHEDULER->TLC[16].next_TLC = &(SCHEDULER->TLC[17]);
	SCHEDULER->TLC[17].sleep_offset = 3;
	SCHEDULER->TLC[17].next_TLC = &(SCHEDULER->TLC[18]);
	SCHEDULER->TLC[18].sleep_offset = 0;
	SCHEDULER->TLC[18].next_TLC = &(SCHEDULER->TLC[19]);
	SCHEDULER->TLC[19].sleep_offset = 0;
	SCHEDULER->TLC[19].next_TLC = &(SCHEDULER->TLC[20]);
	SCHEDULER->TLC[20].sleep_offset = 0;
	SCHEDULER->TLC[20].next_TLC = &(SCHEDULER->TLC[21]);
	SCHEDULER->TLC[21].sleep_offset = 12;
	SCHEDULER->TLC[21].next_TLC = &(SCHEDULER->TLC[22]);
	SCHEDULER->TLC[22].sleep_offset = 6;
	SCHEDULER->TLC[22].next_TLC = NULL;
	SCHEDULER->sleeping_end = &(SCHEDULER->TLC[22]);
	*/
    // some debug printouts
	//printf("\n PCB: %u", sizeof(SCH_PCB));
	//printf("\n PDB: %u", sizeof(SCH_PDB));
	//printf("\n TCB: %u", sizeof(SCH_TCB));
	//printf("\n TLC: %u", sizeof(SCH_TLC));
	//printf("\n CLS: %u", sizeof(SCH_CLS));
	//printf("\n SDA: 0x%x", sizeof(SCH_SDA));

	//printf("\n %u %u %u %u", sizeof(SCH_PCB*), sizeof(SCH_PDB*), sizeof(SCH_TCB*), sizeof(void*));

	//printf("\n 0x%a", KOFFSET);
	//printf("\n 0x%a", SCHEDULER);

	// finished
	return;
}


/*
	routine to set the number of operating CPUs in the system

	param1 = number of operating CPUs
*/
void SetWorkingCPUCount(uint32_t CPUs)
{
	// set the scheduler CPU count
	SCHEDULER->active_CPU_count = CPUs;

	if(CPUs > 1)
	{
		SCHEDULER->CPU_selection_start = 1;
	}

	return;
}

/*
	routine to enable system-wide scheduling

	params - none

	returns nothing
*/
void StartScheduling(void)
{
    // set the scheduler to enabled
    SCHEDULER->scheduling_state = SCHEDULING_STATE_ENABLED;

	// finished
	return;
}




